#include "bulk_storage.h"

/* Error handling libraries. */
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <malloc.h>
/* Found these macros on the web to cope w/ braindead error handling in 
    the IO libraries. **/

/* There is a problem here--The macro consumes the return value of fn;
   instead it should return it.  Otherwise, it would be used
   consistently in this file. */

#define ERR_MSG( fn ) { (void)fflush(stderr); \
                        (void)fprintf(stderr, __FILE__ ":%d:" #fn ": %s\n", \
			              __LINE__, strerror(errno)); }
#define METAPRINTF( fn, args, exp ) if( fn args exp ) ERR_MSG( fn )

#define PRINTF(args)    METAPRINTF(printf, args, < 0)
#define LSEEK(args)     METAPRINTF(lseek64, args, < 0)
#define CLOSE(args)     METAPRINTF(close, args, < 0)
#define READ_OR_DONT(args)      METAPRINTF(read, args, < 0)
#define WRITE_OR_DONT(args)      METAPRINTF(write, args, < 0)
#define READ(args)      METAPRINTF(read, args, <= 0)
#define WRITE(args)      METAPRINTF(write, args, <= 0)


/* Didn't get these to work... 
 #define METARET( val, fn, args, exp ) if ( ( val = fn args ) exp ) { perror("val = fn ( args ) > exp failed."); }
 / *ERR_MSG ( fn )

 #define OPEN(fh, args)  METARET( fs , open, args, < 0)
 */

/** 
   Instead of doubling in size whenever the file runs out of room,
   grow by a fixed number of blocks.  Doesn't make much difference,
   space wise. (At least for the atom parser.  Does slow us down quite
   a bit though...
*/

//#define GROW_BLOCK_COUNT 128


void unpack_bits(int *, const unsigned char);
unsigned char pack_bits(const int *);
void ds_mark_block(struct fileStore *, off_t block, int mark);
void ds_grow_files(struct fileStore * fs, off_t newSize);
off_t ds_find_free_block(struct fileStore * fs, off_t startBlock, off_t stopBlock);

struct fileStore * ds_open(const char * filename, const char * fatname, size_t blockSize, off_t blockCount) {
  off_t realBlockCount = 0;
  struct fileStore* fs = (struct fileStore*) malloc(sizeof(struct fileStore));

  /*  fs->data_file = open(filename, O_RDWR|O_CREAT|O_LARGEFILE, S_IRWXU); */
  fs->data_file = open64(filename, O_RDWR|O_CREAT, S_IRWXU);
  if(fs->data_file < 0) {
    perror("");
  }
  /* fs->fat_file = open(fatname, O_RDWR|O_CREAT|O_LARGEFILE, S_IRWXU); */
  fs->fat_file = open64(fatname, O_RDWR|O_CREAT, S_IRWXU);
  if(fs->fat_file < 0) {
    perror("");
  }

  if(blockCount < 8) {
    blockCount = 8;
  }
  blockCount = (blockCount / 8) * 8;  /* Round blockCount down to the nearest multiple of 8. */

  debug(TRIVIAL, ("blockCount %x\n", (unsigned int) fs->blockCount));

  fs->blockSize = blockSize;
  /*  fs->blockCount = 0; */
  fs->newLastBlock = 0;
  fs->calledFree = 0;

  realBlockCount = lseek64(fs->fat_file, 0, SEEK_END) * 8;

  if(realBlockCount < blockCount) {
    fs->blockCount = realBlockCount;
    ds_grow_files(fs, blockCount);
    fs->blockCount = blockCount;

  } else {
    fs->blockCount = realBlockCount;
  }
  assert(!(fs->blockCount % 8));
  return fs;
}

void ds_close(struct fileStore * fs) {
  CLOSE((fs->fat_file));
  CLOSE((fs->data_file));
  free(fs);
}

void * ds_readBlock(const struct fileStore * fs, off_t blockNumber) {
  void * retVal;
  LSEEK((fs->data_file, blockNumber * fs->blockSize, SEEK_SET));
  retVal = malloc(fs->blockSize /* * sizeof(char)*/);
  //READ((fs->data_file, retVal, fs->blockSize));
  read(fs->data_file, retVal, fs->blockSize);
  return retVal;
} 

void ds_releaseBlock(const struct fileStore * fs, off_t blockNumber, void * block) {
  free(block);
}

void ds_writeBlock(void * value, struct fileStore * fs, off_t blockNumber) {
  LSEEK((fs->data_file, blockNumber * fs->blockSize, SEEK_SET));
  WRITE((fs->data_file, value, fs->blockSize));
}

void ds_writeBulk(void * value, struct fileStore * fs, off_t blockNumber, size_t blockCount) {
  LSEEK((fs->data_file, blockNumber * fs->blockSize, SEEK_SET));
  WRITE((fs->data_file, value, fs->blockSize * blockCount));
}

/**
   This function round-robins through the FAT looking for a free block.
   If none is found, it expands the fat and data files. 
   
 */

off_t ds_newBlock(struct fileStore * fs) {

  /* 8 blocks per byte. */
  off_t current_block = fs->newLastBlock; 
  off_t initial_block = fs->newLastBlock; 
  off_t newBlock = -1;
  
  newBlock = ds_find_free_block(fs, current_block, fs->blockCount);
#ifdef VERBOSE
  printf("Forward scan: %d\n", newBlock);
#endif
  if(newBlock != -1) {
    ds_mark_block(fs, newBlock, 1);
    fs->newLastBlock = newBlock+1;
    return newBlock;
  }

  /* seek to beginning of file to reclaim freed space. */

  if(fs->calledFree) {
    fs->calledFree = 0;
    current_block = 0;
    
    newBlock = ds_find_free_block(fs, current_block, initial_block);
#ifdef VERBOSE
    printf("Beginning scan: %d\n", newBlock);
#endif
    if(newBlock != -1) {
      ds_mark_block(fs, newBlock, 1);
      fs->newLastBlock = newBlock+1;
      return newBlock;
    }
  }

  /* Nothing before the counter, so the file is 100% utilized. */

  /* Grow file by doubling its capacity. */

  newBlock = fs->blockCount;
  fs->newLastBlock = newBlock+1;
#ifdef VERBOSE
  printf("\nds_grow_files(%d)\n", fs->blockCount * 2);
#endif
#ifndef GROW_BLOCK_COUNT
  ds_grow_files(fs, fs->blockCount * 2);
  fs->blockCount *= 2;
#else
  ds_grow_files(fs, fs->blockCount + GROW_BLOCK_COUNT);
  fs->blockCount += GROW_BLOCK_COUNT;
#endif
#ifdef VERBOSE  
  printf("File expand: %d\n", newBlock);
#endif
  ds_mark_block(fs, newBlock, 1);

  return newBlock;

} 



void ds_mark_block(struct fileStore * fs, off_t blockNumber, int used) {
  unsigned char c;
  int bitSet[8];
#ifdef VERBOSE
  printf("Marking %d %d\n", blockNumber, used);
#endif
  LSEEK((fs->fat_file, blockNumber / 8, SEEK_SET));
  READ((fs->fat_file, &c, sizeof(unsigned char)));
  unpack_bits(bitSet, c);
  /*  assert(bitSet[blockNumber % 8] != used);*/  /* Otherwise, attempt to free free block. */
  bitSet[blockNumber % 8] = used;
  c = pack_bits(bitSet);
  LSEEK((fs->fat_file, -1, SEEK_CUR));
  WRITE((fs->fat_file, &c, sizeof(unsigned char)));
}

void ds_freeBlock(struct fileStore * fs, off_t blockNumber) {
  ds_mark_block(fs, blockNumber, 0);
}


off_t ds_find_free_block(struct fileStore * fs, off_t startBlock, off_t stopBlock) {
  off_t i;
  int j;
  unsigned char c;
  int notEOF;
  int bitSet[8];
  off_t start_offset, stop_offset;


#ifdef VERBOSE
  printf("find free: start = %ld, stop = %ld\n", startBlock, stopBlock);
#endif
  /** Expand boundaries so they are multiples of 8. **/
  startBlock = (startBlock / 8) * 8;
  stopBlock = (1+((stopBlock-1) / 8)) * 8;

  if(stopBlock > fs->blockCount) {
    stopBlock = fs->blockCount;
  }

  start_offset = startBlock / 8;
  stop_offset  = stopBlock / 8;

  LSEEK((fs->fat_file, start_offset, SEEK_SET));

  notEOF = read(fs->fat_file, &c, sizeof(unsigned char));
  for(i = start_offset; i < stop_offset && notEOF; i++) {
    if(c != 0xFF) {
      unpack_bits(bitSet, c);
      for(j = 0; j < 8; j++) {
	if(! bitSet[j]) {
#ifdef VERBOSE
	  printf("returning: %ld\n", i*8+j);
#endif
	  return (i*8)+j;
	}
      }
    }
    notEOF = read(fs->fat_file, &c, sizeof(unsigned char));
  }
#ifdef VERBOSE
  printf("returning: -1\n", i+j);
#endif
  return -1;
}

void ds_grow_files(struct fileStore * fs, off_t newSize) {
  off_t i;
#ifdef VERBOSE
  printf("ds_grow_files(%d)\n", newSize);
#endif
  assert(newSize > fs->blockCount);
  LSEEK((fs->data_file, newSize * fs->blockSize, SEEK_SET));
  for(i = 0; i < fs->blockSize; i++) {
    WRITE((fs->data_file, "\0", sizeof(char)));
  } 
  LSEEK((fs->fat_file, newSize / 8, SEEK_SET));
  WRITE((fs->fat_file, "\0", sizeof(char)));
}
size_t ds_blockSize(const struct fileStore * fs) {
  return fs->blockSize;
}
/**
---------------------------------------------------

   Utility functions below this point.

---------------------------------------------------
**/

/**
   Store c bitwise in bitSet, lsb(c) = bitSet[0].
 */
void unpack_bits(int * bitSet, const unsigned char c) {
  int i;
  unsigned char c2 = c;
  for(i = 0; i < 8; i++) {
    bitSet[i] = c2 % 2;
    c2 /= 2;
  }
}


/**
   Store bitSet as a char, bitSet[0] = lsb(c);
 */
unsigned char pack_bits(const int*  bitSet) {
  int i;
  unsigned char c = 0;
  /*Need to traverse the bitSet backwards.*/
  for(i = 7; i >= 0; i--) {
    c *= 2;
    c += bitSet[i]; /* Each entry of bitSet must be 0 or 1...*/
  }
  return c;
}


#define BUF_SIZE 65536

int ds_prepareForBackup(struct fileStore * fs, const char * data, const char * fat) {

  char * data_tilde = malloc(sizeof(char) * (strlen(data) + 2));
  char * fat_tilde = malloc(sizeof(char) * (strlen(fat) + 2));


  strcpy(data_tilde, data);
  strcpy(fat_tilde, fat);
  strcat(data_tilde, "~");
  strcat(fat_tilde, "~");

  if(rename(data, data_tilde) == -1) {
    if(errno != ENOENT) {
      perror("ds_prepareForBackup: Couldn't make a backup of the backup data file.  Attempting clean shutdown.");


      fflush(0);
      return 0;
    }
  }
  if(rename(fat,  fat_tilde) == -1) {
    if(errno != ENOENT) {
      perror("ds_prepareForBackup: Couldn't make a backup of the backup data fat file.  Attempting clean shutdown.");


      fflush(0);
      return 0;
    }
  }

  free(data_tilde);
  free(fat_tilde);

  return 1;

}

int ds_dumpBackup(struct fileStore * fs, const char * data, const char * fat) {

  int data_backup;
  int fat_backup;

  char * buf = malloc(sizeof(char) * BUF_SIZE);
  ssize_t read_count;

  data_backup = open64(data, O_RDWR|O_CREAT, S_IRWXU);
  if(data_backup < 0) {
    perror("Error opening backup file.  Attempting clean shutdown.");

    fflush(0);
    return 0;

  }                   
  /* fs->fat_file = open(fatname, O_RDWR|O_CREAT|O_LARGEFILE, S_IRWXU); */
  fat_backup = open64(fat, O_RDWR|O_CREAT, S_IRWXU);
  if(fat_backup < 0) {
    perror("Error opening backup fat.  Attempting clean shutdown.");

    fflush(0);
    return 0;
  }



  lseek(fs->data_file, 0, SEEK_SET);
  lseek(data_backup, 0, SEEK_SET);

  while((read_count = read(fs->data_file, buf, BUF_SIZE))) {

    if(read_count == -1) {
      perror("fs_dumpBackup: Error reading dataStore.  Attempting clean shutdown.");

      fflush(0);
      return 0;
    }
    if(write(data_backup, buf, read_count) == -1) {
      perror("fs_dumpBackup: Error writing dataStore backup.  Attempting clean shutdown.");

      fflush(0);
      return 0;
      
    }

  }

  lseek(fs->fat_file, 0, SEEK_SET);
  lseek(fat_backup, 0, SEEK_SET);

  while((read_count = read(fs->fat_file, buf, BUF_SIZE))) {

    if(read_count == -1) {
      perror("fs_dumpBackup: Error reading dataStore fat.  Attempting clean shutdown.");

      fflush(0);
      return 0;
    }
    if(write(fat_backup, buf, read_count) == -1) {
      perror("fs_dumpBackup: Error writing dataStore backup fat.  Attempting clean shutdown.");

      fflush(0);
      return 0;
      
    }

  }
  
  if(fsync(data_backup) == -1) {
      perror("fs_dumpBackup: Error synchronizing backup with disk.  Attempting clean shutdown.");

      fflush(0);
      return 0;   
  }
  if(fsync(fat_backup) == -1) {
      perror("fs_dumpBackup: Error synchronizing backup fat with disk.  Attempting clean shutdown.");

      fflush(0);
      return 0;   
  }

  if(close(data_backup) == -1) {
      perror("fs_dumpBackup: Error closing backup (fsync succeeded, so the backup is probably valid).  Attempting clean shutdown.");

      fflush(0);
      return 0;   
  }
  if(close(fat_backup) == -1) {
      perror("fs_dumpBackup: Error closing backup fat (fsync succeeded, so the backup is probably valid).  Attempting clean shutdown.");

      fflush(0);
      return 0;   
  }

  

  free (buf);

  return 1;

}
